package dnbW.dbW.sucxentdbW;

import dnbW.dbW.DataSource;
import dnbW.entityW.Attribute;
import dnbW.entityW.Cardinality;
import dnbW.entityW.Entity;
import dnbW.entityW.EntityIterator;
import dnbW.entityW.IEntity;
import dnbW.entityW.IPath;
import dnbW.entityW.Relationship;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 *
 * @author truongbaquan
 */
public class EntityQueryProcessor {
    public static final double K = 0.8;

    private SucxentDataSource dataSource;

    public EntityQueryProcessor(SucxentDataSource dataSource) {
        this.dataSource = dataSource;
    }

    public List<Entity> getRelevantEntities(String[] keywords) throws SQLException {
        for (String keyword : keywords) {
            System.out.println("Keyword '" + keyword + "'");
            Collection<MatchedAttribute> matchedAttributes = this.getMatchingAttributes(keyword);
            Map<IEntity, RelevantEntity> relevantEntities = new HashMap<IEntity, RelevantEntity>();
            for (MatchedAttribute matchedAttribute : matchedAttributes) {
                System.out.println("\t\t" + matchedAttribute.leafPath.toPathExp() + "\t" + matchedAttribute.frequency);
                IEntity entity = this.dataSource.getEntityManager().findEntityFromPath(matchedAttribute.leafPath);
                if (relevantEntities.containsKey(entity)) {
                    RelevantEntity relevantEntity = relevantEntities.get(entity);
                    relevantEntity.score += matchedAttribute.frequency;
                }
                else {
                    RelevantEntity relevantEntity = new RelevantEntity(entity, matchedAttribute.frequency);
                    relevantEntities.put(entity, relevantEntity);
                }
            }
            
            for (RelevantEntity relevantEntity : relevantEntities.values()) {
                System.out.println("Entity related to " + relevantEntity.entity.getPath().toPathExp());
                EntityRelevanceScoreCalculator calculator = new EntityRelevanceScoreCalculator(relevantEntity, EntityIterator.Direction.Up);
                for (int i = 0; i < 20; i++) {
                    RelevantEntity temp = calculator.getNextRelevantEntity();
                    if (temp != null)
                        System.out.println(temp.getPath().toPathExp() + "\t" + temp.score);
                    else
                        break;
                }

                System.out.println();
            }

        }
        return null;
    }

    public IEntity getRelevantEntity(String keyword) {
        try {
            Collection<MatchedAttribute> attribs = this.getMatchingAttributes(keyword);
            int maxFreq = 0; MatchedAttribute maxAttrib = null;
            for (MatchedAttribute attrib : attribs) {
                if (attrib.frequency > maxFreq) {
                    maxFreq = attrib.frequency;
                    maxAttrib = attrib;
                }
            }
            IEntity entity = this.dataSource.getEntityManager().findEntityFromPath(maxAttrib.leafPath);
            return entity;
        }
        catch (SQLException ex) {
            System.out.println(ex.getMessage());
            ex.printStackTrace();
            return null;
        }
    }

    private Collection<MatchedAttribute> getMatchingAttributes(String keyword) throws SQLException {
        List<MatchedAttribute> returnResult = new ArrayList<MatchedAttribute>();
        Statement statement = this.dataSource.getDatabase().getDbConnection().createStatement();
        statement.executeUpdate("USE [" + this.dataSource.getDatabase().getDbName() + "];");
        String sql = "with FullTable(DocId, PathId, LeafValue) as (\n" +
                    "select DocId, PathId, LeafValue from #DataSrc#_Attribute\n" +
                    "union all\n" +
                    "select DocId, PathId, LeafValue from #DataSrc#_PathValue)\n" +
                    "select PathId, COUNT(*) as Freq from FullTable where ' ' + LeafValue + ' ' like '% #Keyword#[ ,.!?]%'\n" +
                    "group by PathId order by Freq desc\n";
        sql = sql.replaceAll("#DataSrc#", dataSource.getName()).replace("#Keyword#", keyword);
        ResultSet sqlResult = statement.executeQuery(sql);
        while (sqlResult.next()) {
            ILeafPath leafPath = dataSource.getLeafPathManager().getPath(sqlResult.getInt("PathId"));
            int freq = sqlResult.getInt("Freq");
            MatchedAttribute matchedAttribute = new MatchedAttribute(leafPath, freq);
            returnResult.add(matchedAttribute);
        }
        return returnResult;
    }

//    private Set<RelevantEntity> getRelevantEntities(Collection<RelevantEntity> relevantEntities) {
//        Set<RelevantEntity> returnResult = new TreeSet<RelevantEntity>(new ScoreComparator());
//
//        for (RelevantEntity relevantEntity : relevantEntities) {
//            returnResult.add(relevantEntity);
//        }
//    }

    private class EntityRelevanceScoreCalculator {
        Map<IEntity, RelevantEntity> relevantEntities = new HashMap<IEntity, RelevantEntity>();
        EntityIterator entityIterator;

        public EntityRelevanceScoreCalculator(RelevantEntity relevantEntity, EntityIterator.Direction direction) {
            this.entityIterator = new EntityIterator(relevantEntity.entity, direction);
            this.relevantEntities.put(relevantEntity.entity, relevantEntity);
        }

        public RelevantEntity getNextRelevantEntity() {
            Object obj = this.entityIterator.next();
            RelevantEntity relevantEntity = null;
            if (obj instanceof IEntity) {
                IEntity entity = (IEntity)obj;
                if (this.relevantEntities.containsKey(entity)) {
                    relevantEntity = this.relevantEntities.get(entity);
                }
                else {
                    IEntity sourceEntity = this.entityIterator.getCurSource();
                    RelevantEntity source = this.relevantEntities.get(sourceEntity);
                    relevantEntity = new RelevantEntity(entity, source.getScore()*K);
                    this.relevantEntities.put(entity, relevantEntity);
                }
            }
            return relevantEntity;
        }
    }

    // <editor-fold defaultstate="collapsed" desc="class MatchedAttribute">
    private class MatchedAttribute {
        private ILeafPath leafPath;
        private int frequency;

        public MatchedAttribute(ILeafPath leafPath, int frequency) {
            this.leafPath = leafPath;
            this.frequency = frequency;
        }
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="class RelevantEntity">
    private class RelevantEntity implements IEntity {
        // <editor-fold defaultstate="collapsed" desc="Decorator Pattern">
        private IEntity entity;

        @Override
        public DataSource getDataSource() {
            return this.entity.getDataSource();
        }

        @Override
        public String[] getElements() {
            return this.entity.getElements();
        }

        @Override
        public String getElementAtLevel(int level) {
            return this.entity.getElementAtLevel(level);
        }

        @Override
        public boolean isAttribute() {
            return this.entity.isAttribute();
        }

        @Override
        public int getLevel() {
            return this.entity.getLevel();
        }

        @Override
        public String toSQLPathExp() {
            return this.entity.toSQLPathExp();
        }

        @Override
        public String toPathExp() {
            return this.entity.toPathExp();
        }

        @Override
        public IPath getPath() {
            return entity.getPath();
        }

        @Override
        public Relationship getRelationship(IEntity other) {
            return entity.getRelationship(other);
        }

        @Override
        public Collection<Relationship> getRelationships() {
            return entity.getRelationships();
        }

        @Override
        public Cardinality getCardinality(Relationship relationship) {
            return entity.getCardinality(relationship);
        }

        @Override
        public Set<IEntity> getRelatedEntities() {
            return entity.getRelatedEntities();
        }

        @Override
        public void addRelationship(IEntity other, Relationship relationship) {
            entity.addRelationship(other, relationship);
        }

        @Override
        public Collection<Attribute> getAttributes() {
            return entity.getAttributes();
        }

        @Override
        public void addAttribute(Attribute attribute) {
            entity.addAttribute(attribute);
        }
        // </editor-fold>

        private double score;

        public double getScore() {
            return score;
        }

        public RelevantEntity(IEntity entity, double score) {
            this.entity = entity;
            this.score = score;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final RelevantEntity other = (RelevantEntity) obj;
            if (this.entity != other.entity && (this.entity == null || !this.entity.equals(other.entity))) {
                return false;
            }
            return true;
        }

        @Override
        public int hashCode() {
            int hash = 5;
            hash = 37 * hash + (this.entity != null ? this.entity.hashCode() : 0);
            return hash;
        }
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="class ScoreComparator">
    private class ScoreComparator implements Comparator<RelevantEntity> {

        @Override
        public int compare(RelevantEntity o1, RelevantEntity o2) {
            int result = 0;
            if (o1.score - o2.score < 0) result = -1;
            else if (o1.score - o2.score > 0) result = 1;
            return result;
        }

    }
    // </editor-fold>
}
